import tkinter as tk
import socket
import threading
from tkinter import ttk
import subprocess
import re

global current_ip_list
current_ip_list = []

def central_win(win):
    win.resizable(0,0)                      # 不可缩放
    screenwidth = win.winfo_screenwidth()	# 获取屏幕分辨率宽
    screenheight = win.winfo_screenheight()	# 获取屏幕分辨率高
    win.update()	# 更新窗口
    width = win.winfo_width()	# 重新赋值
    height = win.winfo_height()
    size = '+%d+%d' % ((screenwidth - width)/2, (screenheight - height)/2)
    # 重新赋值大小 大小为屏幕大小/2
    win.geometry(size) 	# 以新大小定义窗口

global current_ip_name
current_ip_name = []

def ping_name(num,current_ip):
    global current_ip_name
    ip = current_ip_name[num][0]
    
    name = ""
    group = ""

    r_list = []
    flag = 0

    try:
        result = subprocess.run(['nbtstat', '-A',ip], 
                    capture_output=True, 
                    text=True,
                    creationflags=subprocess.CREATE_NO_WINDOW)
        s = result.stdout
        if current_ip not in s:
            return
        s_list = s.split("\n")
        for i in s_list:
            if current_ip in i:
                flag = 1
            if flag:                
                if "0.0.0.0" in i:
                    break
                if "<" in i:
                    r_list.append(i.split("<")[0].replace(" ",""))
        if not r_list:
            return
        
        name = r_list[0]
        for i in r_list:
            if i == name:
                continue
            else:
                group = i
        if group == "":
            group = name
        current_ip_name[num][2] = name
        current_ip_name[num][3] = group
    except:
        pass

def ping_ip_name(ip_list,net_ip,current_ip):
    global current_ip_name
    current_ip_name = []

    for i in range(len(ip_list)):
        current_ip_name.append(ip_list[i].copy())
        threading.Thread(target=ping_name,args=(i,current_ip,)).start()

    subprocess.run(['nbtstat', '-A',net_ip], 
            capture_output=True, 
            text=True,
            creationflags=subprocess.CREATE_NO_WINDOW)


def ping_ip(ip):
    global current_ip_list
    try:
        result = subprocess.run(['ping', '-n','3',ip], 
                    capture_output=True, 
                    text=True,
                    creationflags=subprocess.CREATE_NO_WINDOW)
        if "TTL" in str(result.stdout):
            current_ip_list.append(ip)
    except:
        pass

def ping_ip_net(ip_net):   
    for i in range(1,256):
        ip = ip_net+str(i)
        threading.Thread(target=ping_ip,args=(ip,)).start()
    
    subprocess.run(['ping', '-n','4',ip_net+'1'], 
                capture_output=True, 
                text=True,
                creationflags=subprocess.CREATE_NO_WINDOW)
        
class DeviceScanner:
    def __init__(self, master):
        self.master = master        
        master.geometry("800x600")  # 设置初始窗口大小
        master.title("Network Device Scanner")
        central_win(self.master)
        
        # 配置行列自适应
        master.rowconfigure(3, weight=1)
        master.columnconfigure(1, weight=1)  # 主机名输入框列权重
        master.columnconfigure(0, weight=1)   # 左侧列权重
        
        # IP显示区域
        self.ip_label = tk.Label(master, text="Current IP: Fetching...")
        
        # 扫描按钮
        self.scan_button = tk.Button(master, text="Start Scan", command=self.start_scan, width=100)
        
        # 新增刷新按钮
        self.refresh_btn = ttk.Button(master, text="↻", width=2, command=self.get_local_ip)

        # 添加线程控制相关变量
        self.stop_flag = False
        self.active_threads = []

        # 结果输出区域
        self.text_area = tk.Text(master, height=8, width=100)
        
        self.tree = ttk.Treeview(master, columns=('IP', 'MAC', '主机名', '工作组'), show='headings', height=8)
        self.tree.heading('IP', text='IP地址')
        self.tree.heading('MAC', text='MAC地址')
        self.tree.heading('主机名', text='主机名')
        self.tree.heading('工作组', text='工作组')
        self.tree.column('IP', width=120)
        self.tree.column('MAC', width=150)
        self.tree.column('主机名', width=180)
        self.tree.column('工作组', width=180)

        # 添加滚动条
        scrollbar = ttk.Scrollbar(master, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscroll=scrollbar.set)

        self.ip_combobox = ttk.Combobox(master, state="readonly")
        self.ip_combobox.bind("<<ComboboxSelected>>", self.update_gateway)

        # 新增主机名测试组件
        self.host_entry = ttk.Entry(master)
        self.host_entry.insert(0,"RADXA-ZERO3")
        self.ping_btn = ttk.Button(master, text="Ping主机", command=self.goto_ping_hostname)

        # 第一行组件
        self.ip_label.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
        self.ip_combobox.grid(row=0, column=1, padx=5, sticky=tk.EW)
        self.host_entry.grid(row=0, column=3, padx=5, sticky=tk.EW)
        self.refresh_btn.grid(row=0, column=2, padx=(0,5), sticky=tk.E)

        # 第二行组件
        self.scan_button.grid(row=1, column=0, columnspan=3, padx=10, pady=5, sticky=tk.EW)
        self.ping_btn.grid(row=1, column=3, padx=5, pady=5, sticky=tk.EW)

        # 下方区域
        self.text_area.grid(row=2, column=0, columnspan=4, padx=10, pady=5, sticky='nsew')
        self.tree.grid(row=3, column=0, columnspan=4, padx=(10, 0), pady=5, sticky='nsew')
        scrollbar.grid(row=3, column=5, padx=(0, 10), sticky='nsew')  # 调整滚动条位置

        self.device_list = []
        # 初始化获取IP
        self.get_local_ip()

    def ping_hostname(self):
        """执行ping命令解析主机名"""        
        i=0
        hostname = self.host_entry.get()
        hostname = hostname.upper()
        self.host_entry.delete(0,tk.END)
        self.host_entry.insert(0,hostname)
        if hostname:
            try:
                result = subprocess.run(['ping', '-4','-n', '1', hostname],
                                    capture_output=True,
                                    text=True,
                                    creationflags=subprocess.CREATE_NO_WINDOW)

                # 匹配带方括号的IP地址
                ip_match = re.search(r'\[([\d.]+)\]', result.stdout)
                if ip_match:
                    ip = ip_match.group(1)
                    self.text_area.insert(tk.END, f"解析成功: {hostname} -> {ip}\n")
                    i=1
                else:
                    # 备用匹配模式
                    ip_match = re.search(r'\d+\.\d+\.\d+\.\d+', result.stdout)
                    if ip_match:
                        ip = ip_match.group()
                        self.text_area.insert(tk.END, f"解析成功: {hostname} -> {ip}\n")
                        i=1
                    else:
                        self.text_area.insert(tk.END, f"无法解析主机名: {hostname}\n")
                
                if i == 1:
                    # 在表格中查找IP
                    found = False
                    for item in self.tree.get_children():
                        if self.tree.item(item, 'values')[0] == ip:
                            # 标红匹配行
                            self.tree.tag_configure('found', background='#ff9999')
                            self.tree.item(item, tags=('found',))
                            self.tree.see(item)  # 滚动到该行
                            found = True
                            break
                    if not found:
                        # 插入新行（MAC设为未知）
                        new_item = self.tree.insert('', 'end', 
                                      values=(ip, "请重新扫描","",""),
                                      tags=('found',))
                        self.tree.tag_configure('found', background='#ff9999')
                        self.tree.see(new_item)  # 滚动到新条目
            except Exception as e:
                self.text_area.insert(tk.END, f"Ping测试失败: {str(e)}\n")
        else:
            self.text_area.insert(tk.END, f"请输入主机名\n")

        self.text_area.see(tk.END)

        self.scan_button.config(state=tk.NORMAL)
        self.ping_btn.config(state=tk.NORMAL)

    def goto_ping_hostname(self):
        self.scan_button.config(state=tk.DISABLED)
        self.ping_btn.config(state=tk.DISABLED)

        threading.Thread(target=self.ping_hostname).start()
    
    def get_local_ip(self):
        try:
            ips = []
            # 获取所有网络接口的IP
            hostname = socket.gethostname()
            ip_list = socket.gethostbyname_ex(hostname)[2]
            
            # 新增UDP方式获取的活动接口IP
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
                s.connect(("8.8.8.8", 80))
                active_ip = s.getsockname()[0]
                if active_ip not in ip_list:
                    ip_list.append(active_ip)

            # 筛选私有地址并排序
            for ip in sorted(ip_list, key=lambda x: x != active_ip):
                if any([ip.startswith('192.168.'),
                       ip.startswith('10.'),
                       (ip.startswith('172.') and 15 < int(ip.split('.')[1]) < 32)]):
                    ips.append(ip)

            # 更新下拉框
            self.ip_combobox['values'] = ips
            if ips:
                self.ip_combobox.set(ips[0])
                self.update_gateway()
            else:
                self.ip_label.config(text="No valid IP found")

        except Exception as e:
            self.text_area.insert(tk.END, f"Network detection failed: {str(e)}\n")

    def update_gateway(self, event=None):
        selected_ip = self.ip_combobox.get()
        if selected_ip:            
            self.gateway = ".".join(selected_ip.split('.')[0:3]) + ".1"
            self.ip_label.config(text=f"Selected IP: {selected_ip} Net: {self.gateway}")


    def start_scan(self):
        self.scan_button.config(state=tk.DISABLED)
        self.ping_btn.config(state=tk.DISABLED)
        self.stop_flag = False
        self.text_area.delete(1.0, tk.END)
        self.text_area.insert(tk.END, f"Scanning gateway {self.gateway}...\n")
        self.tree.delete(*self.tree.get_children())
        # 创建扫描线程
        scan_thread = threading.Thread(target=self.scan_network)
        scan_thread.start()

    def stop_scan(self):
        self.stop_flag = True
        self.text_area.insert(tk.END, "Scan stopped by user\n")
        self.scan_button.config(state=tk.NORMAL)
        self.ping_btn.config(state=tk.NORMAL)

    def scan_network(self):
        self.active_threads = []
        base_ip = self.gateway
        target_ip = f"{base_ip}"
        current_ip = str(self.ip_combobox.get())
        thread = threading.Thread(target=self.check_device, args=(target_ip,current_ip))
        self.active_threads.append(thread)
        thread.start()

    def check_device(self, net_ip,current_ip):
        if self.stop_flag:
            return
        
        global current_ip_list
        current_ip_list = [] 

        ip_list=net_ip.split(".")
        ip = ip_list[0]+"."+ip_list[1]+"."+ip_list[2]+"."
        try:
            try:
                ping_ip_net(ip)
            except:
                pass
            
            result = subprocess.run(['arp', '-a'], 
                                capture_output=True, 
                                text=True,
                                creationflags=subprocess.CREATE_NO_WINDOW)                      

            self.device_list = []
            li = (str(result).split(current_ip+" ---")[1].split(":")[0]).split("\\n")
            for i in li:
                if ip in i:
                    match = re.search(r'(\d+\.\d+\.\d+\.\d+)\s+([0-9a-fA-F-]+)', i)
                    if match:                       
                        ip_addr, mac = match.groups()                               
                        for j in current_ip_list:
                            if j == ip_addr:                                
                                mac = mac.upper()
                                current_ip_list.remove(j)
                                self.device_list.append([ip_addr,mac,'',''])
                                break
        except Exception as e:
            print(f"Error scanning {net_ip} {current_ip}: {str(e)}")

        self.text_area.insert(tk.END, f"Output IP result\n") 
        self.text_area.insert(tk.END, f"Scaning Hostname...\n")             
        for i in self.device_list:
            self.tree.insert('', 'end', values=(i[0], i[1],i[2],i[3]))  

        global current_ip_name
        current_ip_name = []

        ping_ip_name(self.device_list,net_ip,current_ip)
        self.device_list = current_ip_name.copy()

        self.tree.delete(*self.tree.get_children())
        self.text_area.insert(tk.END, f"Output All result\n")

        for i in self.device_list:
            self.text_area.insert(tk.END, f"IP: {i[0]}  MAC: {i[1]} {i[2]} {i[3]}\n")
            self.tree.insert('', 'end', values=(i[0], i[1],i[2],i[3]))

        self.scan_button.config(state=tk.NORMAL)
        self.ping_btn.config(state=tk.NORMAL)

if __name__ == "__main__":
    root = tk.Tk()
    app = DeviceScanner(root)
    root.mainloop()
